package tom.vertx.core.demo06;

import io.netty.handler.codec.http.HttpHeaderValues;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerOptions;
import lombok.extern.slf4j.Slf4j;

/**
 * 启用ssl，https方式参照NetServer demo TcpServer/TcpClient， HttpServer/HttpClient底层使用的NetServer/NetClient
 * @author ZHUFEIFEI
 */
@Slf4j
public class MyHttpServer extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {

    HttpServerOptions httpServerOptions = new HttpServerOptions();
    httpServerOptions.setAcceptBacklog(10240)
      //启用压缩数据反给客户端, 如果客户端不支持,则发送非压缩数据, 发送前会检查请求头Accept-Encoding是否包含gzip/deflate等信息, 包含则压缩
      .setCompressionSupported(true)
      //压缩级别,默认6, 级别越高压缩度越高, 需要更多的cpu
      //.setCompressionLevel(6)
      //客户端请求需要解压缩支持
      .setDecompressionSupported(true)
      .setIdleTimeout(10000)
      .setReceiveBufferSize(8192)
      .setSendBufferSize(8192);

    vertx.createHttpServer(httpServerOptions)
      //成功建立连接handler
      .connectionHandler(conn -> {
        log.info("new connection => {} ssl => {}", conn.remoteAddress(), conn.isSsl());
      })
      //处理请求handler
      .requestHandler(req -> {
        log.info("request from {} => {} {}", req.remoteAddress(), req.absoluteURI(), req.version());
        //需要接收multi-part数据,则需要提前设置, 比如上传文件
//        req.setExpectMultipart(true);
        //暂存request body内容
        Buffer totalBuf = Buffer.buffer();
        //读取request请求的body handler, 如果body体较大, 会多次调用该方法, 每次buf内包含一部分数据
        req.handler(buf -> {
          log.info("read request body size => {}", buf.length());
          totalBuf.appendBuffer(buf);
        });
        //当请求的所有信息都被读取完毕时, 调用该handler
        req.endHandler(e -> {
          log.info("request data all read completed. body size => {}", totalBuf.length());
          //end handler后,request所有数据都可以获取到
          //req.formAttributes()
        });
        //上传文件处理handler, setExpectMultipart(true)时, 通过该方式获取文件
        req.uploadHandler(upload -> {
          log.info("receive file => {} size => {}", upload.filename(), upload.size());
          //由于上传的文件可能很大, 所以不会将文件存储在buf里, 容易造成内存不足, 所以需要自己读取
          upload.handler(buf -> {
            //读取到一块数据, 会多次调用
            log.info("receive a chunk of file => {} size => {}", upload.name(), buf.length());
            //提供了stream方法,直接将数据流写入文件
            upload.streamToFileSystem("tmp/" + upload.filename());
          });
        });

        //仅支持http2
        req.customFrameHandler(frame -> {
          log.info("receive frame type => {} payload => {}", frame.type(), frame.payload().toString());
        });

        //直接响应内容给客户端, 并添加cookie
        req.response().addCookie(Cookie.cookie("a", "b"));
        //等效
        //req.response().end("Hello : " + req.remoteAddress());
        //当响应体较大时, chunked设置为true, 流式返回数据,
        //req.response().setChunked(true);
        //当chunk设置为true时,每次write,都会写回到客户端
        //不设置chunked这个头,就需要设置content-length头, 不然不通过, write失败
        req.response().putHeader(HttpHeaders.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
        req.response().write("Hello : " + req.remoteAddress());
        //支持server push推数据给client, 必须在调用end之前push. 仅支持http2
        req.response().push(HttpMethod.GET, "/hello.js", ar -> {
          if (ar.succeeded()) {
            //push之后回调的response可以进行write操作, 即使外层已经在push后调用end了
            log.info("push response obj => {}", ar.result().getStatusMessage());
            ar.result().putHeader(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON).end("alert(\"push from server\")");
          } else {
            log.info("push error => {}", ar.cause().toString());
          }
        });
        log.info("request response obj => {}", req.response().getStatusMessage());
        //仅支持http2
        req.response().writeCustomFrame(2, 2, Buffer.buffer("world"));
        //写完所有数据后要调用end结束
        req.response().end();
      })
      //web socket
      .websocketHandler(websocket -> {
        if (!websocket.path().equals("/wskt")) {
          log.info("reject => {}", websocket.remoteAddress());
          websocket.reject();
        } else {
          //异步处理握手验证
          Promise<Integer> promise = Promise.promise();
          websocket.setHandshake(promise.future());
          vertx.<Integer>executeBlocking(code -> {
            log.info("websocket headers => {}", websocket.headers());
            if (websocket.headers().contains("username")) {
              if (websocket.headers().get("username").equals("abc")) {
                log.info("websocket handshake succeed");
                code.complete(101);
              } else {
                log.info("websocket handshake denied");
                code.complete(401);
              }
            } else {
              log.info("websocket handshake failed");
              code.fail("error");
            }
          }, res -> {
            if (res.succeeded()) {
              log.info("websocket succeed => {}", res.result());
              promise.complete(res.result());
            } else {
              log.info("websocket failed => {}", res);
              promise.fail(res.cause());
            }
          });
          websocket.closeHandler(ha -> {
            log.info("websocket closed.");
          });
          websocket.exceptionHandler(exp -> {
            log.warn("websocket exception -> {}", exp.toString());
          });
          websocket.frameHandler(frame -> {
            log.info("websocket frame => {} {} {}", frame.isText(), frame.isFinal(), frame.binaryData().toString());
          });

        }
      })
      .listen(8888, "localhost", res -> {
      if (res.succeeded()) {
        log.info("Http Server Started.");
        startPromise.complete();
      } else {
        log.info("Http Server Start Failed => {}", res.cause().toString());
        startPromise.fail(res.cause());
      }
    });
  }

  public static void main(String[] args) {
    Vertx.vertx().deployVerticle(new MyHttpServer());
  }
}
